package server

import (
	"fmt"
	"github.com/golang/protobuf/proto"
	"go-arch/network/chatroom/constants"
	"go-arch/network/chatroom/pb"
	"go-arch/network/chatroom/types"
	"go-arch/network/chatroom/util"
	"io"
	"log"
	"net"
	"strconv"
	"strings"
	"time"
)

type SimpleServer struct {
	name    string
	address string
	clients map[int32]*net.TCPConn
	rooms   map[string]*types.Room
}

func NewSimpleServer(name, address string) *SimpleServer {
	return &SimpleServer{
		name:    name,
		address: address,
		clients: make(map[int32]*net.TCPConn),
		rooms:   make(map[string]*types.Room),
	}
}

func (s *SimpleServer) Start() error {
	addr, err := net.ResolveTCPAddr("tcp4", s.address)
	if err != nil {
		return err
	}

	listener, err := net.ListenTCP("tcp4", addr)
	if err != nil {
		return err
	}

	for {
		tcpConn, err := listener.AcceptTCP()
		if err != nil {
			log.Println(err)
			continue
		}

		go s.handleConn(tcpConn)
	}
}

func (s *SimpleServer) handleConn(conn *net.TCPConn) {
	for {
		container := make([]byte, 1024)
		read, err := conn.Read(container)

		if err == io.EOF {
			fmt.Printf("read finished caused by client close the conn, err:%v\n", err)
			err := conn.Close()
			if err != nil {
				fmt.Printf("close conn err: %v\n", err)
			}
			break
		}

		if err != nil {
			fmt.Printf("read bytes error caused by err:%v\n", err)
			continue
		}

		if read == 0 {
			fmt.Printf("nothing read, maybe caused by client close the conn\n")
			continue
		}

		bytes := container[:read]
		message := &msg.Msg{}
		err = proto.Unmarshal(bytes, message)
		if err != nil {
			log.Println(err)
			continue
		}
		s.handleMsg(conn, message)
	}
}

func (s *SimpleServer) handleMsg(conn *net.TCPConn, msg *msg.Msg) {
	if msg.Magic != 0x19921110 {
		log.Println("magic number is wrong , so this is illegal message")
		return
	}

	switch msg.Cmd {
	case constants.Login:
		s.handleLoginMsg(conn, msg)
		break
	case constants.CreateRoom:
		s.handleCreateRoomMsg(conn, msg)
		break
	case constants.JoinRoom:
		s.handleJoinRoomMsg(conn, msg)
		break
	case constants.SendMsgToRoom:
		s.handleSendMsgToRoom(conn, msg)
		break
	case constants.SendMsgToUser:
		s.handleSendMsgToUser(conn, msg)
		break
	case constants.ListRoomMembers:
		s.handleListRoomMembers(conn, msg)
		break
	default:
		fmt.Println("ignore", msg)
		break
	}
}

func (s *SimpleServer) handleLoginMsg(conn *net.TCPConn, message *msg.Msg) {
	userId := message.FromId
	if _, ok := s.clients[userId]; ok {
		bytes, err := loginRespMsg(userId, "client has already exist")
		if err != nil {
			log.Printf("proto marshal failed, err: %v\n", err)
		}
		_, err = conn.Write(bytes)
		if err != nil {
			log.Printf("send message to %d failed, err: %v\n", message.FromId, err)
		}
	} else {
		s.clients[userId] = conn
		bytes, err := loginRespMsg(userId, "ok")
		if err != nil {
			log.Printf("proto marshal failed, err: %v\n", err)
		}
		_, err = conn.Write(bytes)
		if err != nil {
			log.Printf("send message to %d failed, err: %v\n", message.FromId, err)
		}
	}

}

func (s *SimpleServer) handleCreateRoomMsg(conn *net.TCPConn, m *msg.Msg) {
	roomName := string(m.Body)
	userId := m.FromId

	if _, ok := s.rooms[roomName]; ok {
		bytes, err := createRoomRespMsg(userId, "room name duplicated")
		if err != nil {
			log.Printf("proto marshal failed, err: %v\n", err)
		}
		_, err = conn.Write(bytes)
		if err != nil {
			log.Printf("send message to %d failed, err: %v\n", m.FromId, err)
		}
	} else {
		roomId := util.RandomId()
		room := &types.Room{
			Id:    roomId,
			Name:  roomName,
			Users: make([]int, 0),
		}
		room.Users = append(room.Users, int(userId))
		s.rooms[roomName] = room
		bytes, err := createRoomRespMsg(userId, strconv.Itoa(roomId))
		if err != nil {
			log.Printf("proto marshal failed, err: %v\n", err)
		}
		_, err = conn.Write(bytes)
		if err != nil {
			log.Printf("send message to %d failed, err: %v\n", m.FromId, err)
		}
	}
}

func (s *SimpleServer) handleJoinRoomMsg(conn *net.TCPConn, m *msg.Msg) {
	fromUserId := m.FromId
	roomName := string(m.Body)
	if room, ok := s.rooms[roomName]; ok {
		room.Users = append(room.Users, int(fromUserId))
		bytes, err := createJoinRoomRespMsg(fromUserId, "ok")
		if err != nil {
			log.Printf("proto marshal failed, err: %v\n", err)
		}
		_, err = conn.Write(bytes)
		if err != nil {
			log.Printf("send message to %d failed, err: %v\n", m.FromId, err)
		}
	} else {
		bytes, err := createJoinRoomRespMsg(fromUserId, fmt.Sprintf("no room for name %s", roomName))
		if err != nil {
			log.Printf("proto marshal failed, err: %v\n", err)
		}
		_, err = conn.Write(bytes)
		if err != nil {
			log.Printf("send message to %d failed, err: %v\n", m.FromId, err)
		}
	}
}

func (s *SimpleServer) handleSendMsgToRoom(conn *net.TCPConn, m *msg.Msg) {
	fromUserId := m.FromId
	roomName := m.Extend
	message := m.Body

	room := s.rooms[roomName]
	for _, userId := range room.Users {
		if userId != int(fromUserId) {
			tcpConn := s.clients[int32(userId)]
			roomMsg, err := buildRoomMsg(fromUserId, int32(userId), message)
			if err != nil {
				log.Printf("proto marshal failed, err: %v\n", err)
				continue
			}
			_, err = tcpConn.Write(roomMsg)
			if err != nil {
				log.Printf("send message to %d failed, err: %v\n", m.FromId, err)
			}
		}
	}
}

func (s *SimpleServer) handleSendMsgToUser(conn *net.TCPConn, m *msg.Msg) {

}

func (s *SimpleServer) handleListRoomMembers(conn *net.TCPConn, m *msg.Msg) {
	roomName := string(m.Body)
	if room, ok := s.rooms[roomName]; ok {
		userIds := joinUserIds(room.Users)
		memberMsg, err := buildListRoomMemberMsg(m.FromId, m.ToId, userIds)
		if err != nil {
			log.Printf("proto marshal failed, err: %v\n", err)
		} else {
			_, err := conn.Write(memberMsg)
			if err != nil {
				log.Printf("send message to %d failed, err: %v\n", m.FromId, err)
			}
		}
	} else {
		memberMsg, err := buildListRoomMemberMsg(m.FromId, m.ToId, fmt.Sprintf("there is no such room called %s", roomName))
		if err != nil {
			log.Printf("proto marshal failed, err: %v\n", err)
		} else {
			_, err := conn.Write(memberMsg)
			if err != nil {
				log.Printf("send message to %d failed, err: %v\n", m.FromId, err)
			}
		}
	}

}

func joinUserIds(users []int) string {
	if len(users) == 0 {
		return ""
	}

	sb := strings.Builder{}
	for _, user := range users {
		sb.WriteString(strconv.Itoa(user))
		sb.WriteString("|")
	}
	i := len(sb.String())
	return sb.String()[:i-1]
}

func buildListRoomMemberMsg(userId, toId int32, users string) ([]byte, error) {
	m := &msg.Msg{
		Magic:     constants.Magic,
		Version:   constants.Version,
		Cmd:       constants.ListRoomMembers,
		MsgType:   constants.Text,
		MsgStatus: constants.Ack,
		MsgId:     int64(util.RandomId()),
		FromId:    userId,
		ToId:      toId,
		Body:      []byte(users),
		Extend:    "",
		Timestamp: int64(time.Now().Nanosecond()),
	}
	bytes, err := proto.Marshal(m)
	return bytes, err
}

func buildRoomMsg(userId int32, toId int32, message []byte) ([]byte, error) {
	m := &msg.Msg{
		Magic:     constants.Magic,
		Version:   constants.Version,
		Cmd:       constants.SendMsgToRoom,
		MsgType:   constants.Text,
		MsgStatus: constants.Ack,
		MsgId:     int64(util.RandomId()),
		FromId:    userId,
		ToId:      toId,
		Body:      message,
		Extend:    "",
		Timestamp: int64(time.Now().Nanosecond()),
	}
	bytes, err := proto.Marshal(m)
	return bytes, err
}

func createJoinRoomRespMsg(userId int32, result string) ([]byte, error) {
	m := &msg.Msg{
		Magic:     constants.Magic,
		Version:   constants.Version,
		Cmd:       constants.JoinRoom,
		MsgType:   constants.Text,
		MsgStatus: constants.Ack,
		MsgId:     int64(util.RandomId()),
		FromId:    0,
		ToId:      userId,
		Body:      []byte(result),
		Extend:    "",
		Timestamp: int64(time.Now().Nanosecond()),
	}
	bytes, err := proto.Marshal(m)
	return bytes, err
}

func createRoomRespMsg(userId int32, result string) ([]byte, error) {
	m := &msg.Msg{
		Magic:     constants.Magic,
		Version:   constants.Version,
		Cmd:       constants.CreateRoom,
		MsgType:   constants.Text,
		MsgStatus: constants.Ack,
		MsgId:     int64(util.RandomId()),
		FromId:    0,
		ToId:      userId,
		Body:      []byte(result),
		Extend:    "",
		Timestamp: int64(time.Now().Nanosecond()),
	}
	bytes, err := proto.Marshal(m)
	return bytes, err
}

func loginRespMsg(userId int32, result string) ([]byte, error) {
	m := &msg.Msg{
		Magic:     constants.Magic,
		Version:   constants.Version,
		Cmd:       constants.Login,
		MsgType:   constants.Text,
		MsgStatus: constants.Ack,
		MsgId:     int64(util.RandomId()),
		FromId:    0,
		ToId:      userId,
		Body:      []byte(result),
		Extend:    "",
		Timestamp: int64(time.Now().Nanosecond()),
	}
	bytes, err := proto.Marshal(m)
	return bytes, err
}
